package com.smfx.yxt.excel;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.smfx.yxt.excel.support.ExcelFormat;
import com.smfx.yxt.excel.support.ExcelParseException;
import com.smfx.yxt.excel.support.IExcelParser;
import com.smfx.yxt.excel.support.XLSParser;

/**
 * Excel工具类
 */
public final class ExcelUtils {
    /**
     * Excel工具类 LOG
     */
    protected final static Logger logger = LoggerFactory.getLogger(ExcelUtils.class);

    private final static Map<ExcelFormat, IExcelParser> PARSER_MAP = new ConcurrentHashMap<ExcelFormat, IExcelParser>();

    private ExcelUtils() {

    }

    static {
        PARSER_MAP.put(ExcelFormat.XLS, new XLSParser());
    }

    /**
     * parse excel and export
     * 
     * @param fileName
     * @param context
     * @param out
     */
    public static void export(String fileName, Object context, OutputStream out) {

        HSSFWorkbook wb = WorkbookUtils.openWorkbook(fileName);
        parseWorkbook(context, wb);
        try {
            wb.write(out);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * parse exel and export
     * 
     * @param fileName
     * @param sheetIndex
     * @param out
     */
    public static void export(String fileName, int sheetIndex, Object context, OutputStream out) {

        HSSFWorkbook wb = WorkbookUtils.openWorkbook(fileName);
        parseWorkbook(context, wb, sheetIndex);
        try {
            wb.write(out);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * parse excel and export excel
     * 
     * @param fileName
     * @param out
     */
    public static void export(String fileName, OutputStream out) {
        export(fileName, getContext(), out);
    }

    /**
     * parse excel and export excel
     * 
     * @param fileName
     * @param sheetIndex
     * @param out
     */
    public static void export(String fileName, int sheetIndex, OutputStream out) {

        export(fileName, sheetIndex, getContext(), out);

    }

    /**
     * @param inputStream
     * @param context
     * @param out
     */
    public static void export(InputStream inputStream, Object model, OutputStream out) {
    	HSSFWorkbook wb = WorkbookUtils.openWorkbook(inputStream);
        parseWorkbook(model, wb);
        try {
            wb.write(out);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * parse workbook
     * 
     * @param context
     * @param wb
     */
    public static void parseWorkbook(Object model, HSSFWorkbook wb) {
    	if (model != null) {
		    		// 设置数据模型
		//        	String valueType = model.getClass().getName();
		//        	if("java.util.HashMap".equals(valueType)) {
		//        		addValue((Map<String, Object>)model);
		//        	}else {
		//        		addValue("model", model);
		//        	}
        	addValue("model", model);
        }

        int sheetCount = wb.getNumberOfSheets();
        for (int sheetIndex = 0; sheetIndex < sheetCount; sheetIndex++) {
            HSSFSheet sheet = wb.getSheetAt(sheetIndex);
            parseSheet(sheet);
        }
    }

    /**
     * parse Workbook
     * 
     * @param context
     * @param wb
     * @param sheetIndex
     */
    public static void parseWorkbook(Object context, HSSFWorkbook wb, int sheetIndex) {

        HSSFSheet sheet = wb.getSheetAt(sheetIndex);
        if (null != sheet) {
            parseSheet(sheet);
        }

        int i = 0;
        while (i++ < sheetIndex) {
            wb.removeSheetAt(0);
        }

        i = 1;
        while (i < wb.getNumberOfSheets()) {
            wb.removeSheetAt(i);
        }
    }

    /**
     * parse Excel Template File
     * 
     * @param sheet Workbook sheet
     */
    public static void parseSheet(HSSFSheet sheet) {

        ExcelParser.parse(sheet, sheet.getFirstRowNum(), sheet.getLastRowNum());

    }

    /**
     * 从2003格式的Excel文件中读取内容到Java对象
     *
     * @param is 文件流对象
     * @param type Excel行对应的Java对象
     * @param startRow 从该参数指定的行开始读取数据
     * @param <T> 泛型参数
     * @return Excel数据
     */
    public static <T> ExcelData<T> read(final InputStream is, final Class<T> type, final int startRow) {
        return read(ExcelFormat.XLS, is, null, type, startRow);
    }

    /**
     * 从流中读取文件内容到Java对象
     *
     * @param format Excel文件格式
     * @param is 文件流对象
     * @param type Excel行对应的Java对象
     * @param startRow 从该参数指定的行开始读取数据
     * @param <T> 泛型参数
     * @return Excel数据
     */
    public static <T> ExcelData<T> read(ExcelFormat format, final InputStream is, final Class<T> type,
            final int startRow) {
        return read(format, is, null, type, startRow);
    }

    /**
     * 对于整行都为空的情况，视为无效数据，忽略
     *
     * @param format Excel文件格式
     * @param is 文件流对象
     * @param rowMapper 行转换对象
     * @param type Excel行对应的Java对象
     * @param startRow 从该参数指定的行开始读取数据
     * @param <T> 泛型参数
     * @return Excel数据
     */
    public static <T> ExcelData<T> read(ExcelFormat format, InputStream is, final RowMapper<T> rowMapper,
            final Class<T> type, final int startRow) {
        final ExcelData<T> dataResult = new ExcelData<T>();
        List<List<String>> excelData = null;
        try {
            IExcelParser parser = PARSER_MAP.get(format);
            excelData = parser.parse(is);
        }
        catch (final Exception e) {
            throw new ExcelParseException("Excel文件损坏，无法读取。", e);
        }
        int len = excelData.size();
        for (int row = startRow; row < len; row++) {
            List<String> rowData = excelData.get(row);
            T obj = null;
            try {
                obj = type.newInstance();
            }
            catch (Exception e1) {
                logger.error("解析Excel时，实例化模型类失败", e1);
                throw new ExcelParseException("Excel Model实例化错误，请确保是否存在无参的构造函数。");
            }
            boolean isAllBlank = true; // 整行记录为空标志
            for (final Field field : type.getDeclaredFields()) {
                try {
                    final ExcelColumn e = field.getAnnotation(ExcelColumn.class);
                    if (e != null) {
                        String content = rowData.get(e.pos());
                        if (StringUtils.isNotBlank(content)) {
                            parseElement(e, obj, field.getName(), content);
                            isAllBlank = false;
                        }
                    }
                }
                catch (final Exception e) {
                    logger.error("填充模型数据时出错", e);
                    throw new ExcelParseException(e.getMessage());
                }
            }
            if (isAllBlank) {// 如果整行记录为空，则忽略该行数据
                continue;
            }
            if (rowMapper == null) {
                dataResult.getResultList().add(obj);
            }
            else {
                dataResult.getResultList().add(rowMapper.mapRow(obj, row));
            }
        }
        return dataResult;
    }

    /**
     * 根据注解，将Excel单元格内容更新到Model
     *
     * @param e
     * @param obj
     * @param fieldName
     * @param content
     * @throws Exception
     */
    private static <T> void parseElement(final ExcelColumn e, Object obj, String fieldName, String content)
            throws Exception {
        if (e != null) {
            if (StringUtils.isBlank(content)) {
                if (e.required()) {
                    throw new ExcelParseException(String.format("%s不能为空!", e.name()));
                }
                return;
            }
            PropertyUtils.setProperty(obj, fieldName, convertCotent(content, e));
        }
    }

    /**
     * 根据注解，将字符串转换为不同的数据类型格式
     *
     * @param content
     * @param metaColumn
     * @return
     * @throws Exception
     */
    private static Object convertCotent(String content, ExcelColumn metaColumn) throws Exception {
        Class<?> propType = metaColumn.propType();
        if (Boolean.class.equals(propType)) {
            return Boolean.valueOf(content);
        }
        else if (ClassUtils.primitiveToWrapper(propType).equals(Character.class)) {
            return content.charAt(0);
        }
        else if (Integer.class.isAssignableFrom(ClassUtils.primitiveToWrapper(propType))) {// Excel读取出的数字默认为double格式
            return content.indexOf(".") != -1 ? Double.valueOf(content).intValue() : Integer.parseInt(content);
        }
        else if (Long.class.isAssignableFrom(ClassUtils.primitiveToWrapper(propType))) {
            return content.indexOf(".") != -1 ? Double.valueOf(content).longValue() : Long.parseLong(content);
        }
        else if (Double.class.isAssignableFrom(ClassUtils.primitiveToWrapper(propType))) {
            return Double.valueOf(content);
        }
        else if (propType.equals(String.class)) {
            return content.trim();
        }
        else if (java.util.Date.class.isAssignableFrom(propType)) {
            return DateUtils.parseDate(content, new String[] { metaColumn.format() });
        }
        else if (propType.equals(BigDecimal.class)) {
            return new BigDecimal(content);
        }
        else {
            throw new ExcelParseException("数据类型不支持:" + propType);
        }
    }

    /**
     * can value be show
     * 
     * @param value
     * @return boolean
     */
    public static boolean isCanShowType(Object value) {
        if (null == value)
            return false;
        String valueType = value.getClass().getName();
        return "java.lang.String".equals(valueType) || "java.lang.Double".equals(valueType)
                || "java.lang.Integer".equals(valueType) || "java.lang.Boolean".equals(valueType)
                || "java.sql.Timestamp".equals(valueType) || "java.util.Date".equals(valueType)
                || "java.lang.Byte".equals(valueType) || "java.math.BigDecimal".equals(valueType)
                || "java.math.BigInteger".equals(valueType) || "java.lang.Float".equals(valueType)
                || value.getClass().isPrimitive();
    }

    public static void addService(String key, Object service) {
        addValue(key, service);
    }
    
    public static void addValue(Map<String, Object> map) {
	  for (String key : map.keySet()) {
	   	getContext().set(key, map.get(key));
	  }
    }

    /**
     * add a object to default context
     * 
     * @param key
     * @param value
     */
    public static void addValue(String key, Object value) {
        getContext().set(key, value);
    }

    public static Object getValue(String key) {
        return getContext().findValue(key);
    }

    public static ExcelValueStack getContext() {
        return context.get();
    }

    static ThreadLocal<ExcelValueStack> context = new ThreadLocal<ExcelValueStack>()
    {
        /**
         * @see java.lang.ThreadLocal#initialValue()
         */
        @Override
        protected ExcelValueStack initialValue() {
            return new ExcelValueStack();
        }
    };
}
